Algorithms for Containment/Avoidance
For bounding extremal functions ex(n, P) of forbidden 0-1 matrices P, it can be useful to calculate exact values of the extremal function for small values of n. One way to do this is to check whether any of the n x n matrices with k ones avoid P for increasing values of k. Naive algorithm Suppose A is a h_A\times w_A 0-1 matrix and B is a h_B\times w_B 0-1 matrix. Then one naive algorithm takes O({h_A \choose h_B}{w_A \choose w_B}|B|) time to decide whether A contains B , where |B| is the number of ones in matrix B . Faster algorithms for specific patterns For many specific patterns P , it is possible to determine whether a h_A\times w_A 0-1 matrix contains P in O(w_A\times h_A) time. k \times 1 matrix with all ones One of the easiest cases is a k \times 1 matrix with all ones. If B is k\times 1 with all ones, then an O(w_A\times h_A) algorithm scans A column by column, and whenever it finds any column of A with at least k ones, it stops and determines A contains B . Otherwise A doesn't contain B after the algorithm scans through all columns. k\times k identity matrix If B is a k\times k identity matrix. The following algorithm takes O(w_A\times h_A) time. It maintains an array Dh_Aw_A . For 1\leq r\leq h_A , Drc is the maximum number of ones from column c to the last column of A such that (1) all these ones are in rows between r and h_A , inclusive. (2) all these ones form a Drc\times Drc identity matrix when we remove all columns and rows not containing these ones. Initially Drc is 0 for all r . The algorithm updates D as follows. For c = w_A to 1 For r = h_A to 1 Drc=\max(Dr+1c,Drc+1) if A(r,c) = 1 Drc=\max(Drc, 1 + Dr+1c+1) The algorithm reports A contains B whenever some Drc hits k . Replace any D above with index out of bound by value 0. Tensor of k x k identity matrix with j x 1 matrix of all 1's For matrix B obtained by replacing each one of an identity matrix with a matrix with all ones -- let's denote it by j-column-identity matrix, here is an algorithm to determine whether A contains B in time O(h_A w_A): Drc now indicates the maximum width of j-column-identity matrix contained by the submatrix of A within row r to h_A and column c to w_A. The algorithm reports true if any Drc reaches k. The algorithm proceeds as before, but for each column c and each row r, it needs to know a number r+j <= H(r,c) <= h_A first. H(r,c) is as small as possible such that in column c the number of 1s between row r and H(r,c)-1, inclusive, is at least j. If in column c there are less than j 1s from row r through the bottom we can set H(r,c)= infinity. Since the outer loop of the algorithm scans each column and the inner loop scans each row, simple technique ensures the overhead of computing H(r,c) is O(h_A) per column, and hence doesn't impact overall asymptotic complexity. So the recurrence becomes Drc = max(Dr+1c,Drc+1) If Arc=1 ..Drc = max(Drc,1+DH(r,c)c+1) k x d matrix of all ones An algorithm in time O(w2h) and memory O(w2) can decide whether a given matrix A contains P if P is k x 2. It goes by keeping {w \choose 2} counters and scanning row by row of A. In each row i if both A_{i,a} and A_{i,b} are 1 then counter (a,b) is incremented by 1. The algorithm reports true if any counter hits k. Generalizing to k x 3 and k x 4 patterns, the time complexity goes to O(w3h) and O(w4h), respectively. L-shaped patterns For patterns P_{m x n}=L(m,n), defined as L-shaped 1s with P_{i,j}=1 iff j=1 or i=m, we know ex(n,P)=O(n). And the O(hw) algorithm to determine if A contains P is this: Keep w counters. Scan A row by row from bottom to top. If in current row there are k >= n ones, mark the left k-n+1 columns with ones. Also if there's an one in a column i marked in any of previous rows, increment counter i by 1. Whenever a counter hits m-1, A contains P. Cross-patterns Let P_{a x b} denote a cross pattern, with P(i,j)=1 iff i=c or j=d where c and d are constants such that 1 <= c <= a and 1 <= d <= b. It turns out to also depend on the format the list of ones are given in this particular example. An O(x) algorithm exists if the ones are in the following format. Each one is associated with a distinct node, which has at most 4 links (pointers) to other nodes. One link to the next one to the right in the same row, or if it doesn't exist, the left most one in the next row below. One to the next one to the left in the same row, or if it doesn't exist, the right most one in the next row below...and something similar for vertical links. Each node may have special marks indicating it's the top/bottom/right most/left most one of that column/row. Following these 4 links and marks, we can compute and store R(node), the number of ones to its right in the same row, and similary L(node),U(node),D(node), in 4 linear traversals, i.e. O(x) time. Finally, in one extra traversal the algorithm reports true if a node's L, R, D, and U values are all above respective thresholds. The assumed input data structure and the needed algorithm could be generalized to d-dimensional case. Each node has 2d pointers pointing to its 2d neighbors (actually d pointers work too, just need extra O(x) backtrack time and O(x) memory for backtracking), and 2d counters storing the number of ones beyond itself in each direction. The overall 2d+1 O(x) time traversal could be reduced to 2d, because in the last traversal when the last counter is populated, we can check if all counters are above the thresholds. The requirement that the pointer has to point to (for example) the top ones in the next column if the current node is the bottom one in its column can be avoided. The only reason for this requirement was to enable a smooth traversal. To get rid of it, in each dimension and each direction we can require the node be presented as a list of linked lists, one for each row or column in the 2-D case. And the traversal for all ones in each dimension and direction is then accomplished by traversing each linked list in the list. Therefore, the final data structure is a list of lists of linked lists. Each element in the highest level list corresponds to a dimension and a direction, each element in the second level list is a linked list corresponding to, for example, a column in the 2-D case. Relation to ex(n, P) If the exact value of ex(n,P)=O(n) is known and h=w: To find out if A contains P, we can first count 1s in A. If it exceeds ex(h,P), then A contains P. Otherwise, we're left with O(h) ones. Now if we have an O(x2) algorithm that decides if the given x ones contain P, then we do have an O(h2) algorithm for the original problem. If A is n x n, then any algorithm has to check at least n2-ex(n,P) entries to declare A avoids P, because if it skips ex(n,P)+1 elements, then in the worst case these skipped elements are all 1 and contains P. Since ex(n,P)=o(n2) then the proportion of elements that must be checked before saying A avoids P is close to 1. Open Problems What is the fastest running time for an algorithm to determine whether a 0-1 matrix A contains a 0-1 matrix B with all ones? More specifically, what is the fastest running time for an algorithm to determine whether a 0-1 matrix A contains a k x d matrix B with all ones? Given a 0-1 matrix A as a list e_{1}, ..., e_{x} of all entries where A has ones, can we determine whether A contains P with an O(x2) algorithm? Given a 0-1 matrix A as a list e_{1}, ..., e_{x} of all entries where A has ones, can we determine whether A contains P with an O(x) algorithm? Thread: http://www.artofproblemsolving.com/polymath/mitprimes2016/f/c195578h1246307_creating_a_research_game